const fs = require("fs");
const path = require("path");
const express = require("express");
const bodyParser = require("body-parser");
const multiparty = require("multiparty");
const SparkMD5 = require("spark-md5");

/*-CREATE SERVER-*/
const app = express(),
  PORT = 8888,
  HOST = "http://127.0.0.1",
  HOSTNAME = `${HOST}:${PORT}`;

// 监听服务器
app.listen(PORT, () => {
  console.log(
    `THE WEB SERVICE IS CREATED SUCCESSFULLY AND IS LISTENING TO THE PORT：${PORT}，YOU CAN VISIT：${HOSTNAME}`
  );
});

/*-中间件允许跨域-*/
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  req.method === "OPTIONS"
    ? res.send("CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!")
    : next();
});

// 解析请求体中间件
app.use(
  bodyParser.urlencoded({
    extended: false,
    limit: "1024mb",
  })
);

/*-API-*/

// 延迟函数
const delay = function delay(interval) {
  interval = typeof interval !== "number" ? 1000 : interval; // 默认延迟 1s
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, interval);
  });
};

// 检测文件是否存在
const exists = function exists(path) {
  return new Promise((resolve) => {
    fs.access(path, fs.constants.F_OK, (err) => {
      if (err) {
        resolve(false);
        return;
      }
      resolve(true);
    });
  });
};

// 创建文件并写入到指定的目录 & 返回客户端结果
const writeFile = function writeFile(res, path, file, filename, stream) {
  return new Promise((resolve, reject) => {
    // 是否流文件
    if (stream) {
      try {
        let readStream = fs.createReadStream(file.path), // 创建可读流
          writeStream = fs.createWriteStream(path);
        readStream.pipe(writeStream);
        readStream.on("end", () => {
          resolve();
          fs.unlinkSync(file.path); // (使用传输流传输完成后)删除文件
          res.send({
            code: 0,
            codeText: "upload success",
            originalFilename: filename,
            servicePath: path.replace(__dirname, HOSTNAME),
          });
        });
      } catch (err) {
        reject(err);
        res.send({
          code: 1,
          codeText: err,
        });
      }
      return;
    }
    // 写入的路径和文件数据
    fs.writeFile(path, file, (err) => {
      if (err) {
        reject(err);
        res.send({
          code: 1,
          codeText: err,
        });
        return;
      }
      resolve();
      res.send({
        code: 0,
        codeText: "upload success",
        originalFilename: filename,
        servicePath: path.replace(__dirname, HOSTNAME),
      });
    });
  });
};

// 基于 multiparty 插件实现文件上传处理 & form-data 解析
const uploadDir = path.join(__dirname, "./upload"); // 上传文件地址
const multiparty_upload = function multiparty_upload(req, auto) {
  auto = typeof auto !== "boolean" ? true : auto;
  let config = {
    maxFieldsSize: 200 * 1024 * 1024, // 文件最大限制
  };
  if (auto) {
    // 判断目录是否存在不存在则创建
    fs.stat(uploadDir, function (err, stats) {
      if (!stats) {
        fs.mkdir(uploadDir, { recursive: true }, (err) => {
          if (err) console.log(err);
        });
      }
    });
    config.uploadDir = uploadDir; // 默认是在临时目录 os.tmpdir()
  }
  return new Promise(async (resolve, reject) => {
    await delay();
    new multiparty.Form(config).parse(req, (err, fields, files) => {
      if (err) {
        reject(err);
      } else {
        resolve({ fields, files });
      }
    });
  });
};

// 单文件上传处理「FORM-DATA」
app.post("/upload_single", async (req, res) => {
  try {
    let { files } = await multiparty_upload(req, true);
    let file = (files.file && files.file[0]) || {};
    res.send({
      code: 0,
      codeText: "upload success",
      originalFilename: file.originalFilename,
      servicePath: file.path.replace(__dirname, HOSTNAME),
    });
  } catch (err) {
    res.send({
      code: 1,
      codeText: err,
    });
  }
});

// 前端处理文件名单文件上传
app.post("/upload_single_name", async (req, res) => {
  try {
    let { fields, files } = await multiparty_upload(req, true);
    let file = (files.file && files.file[0]) || {};
    let filename = (fields.filename && fields.filename[0]) || "";
    let filePath = path.resolve(uploadDir, `./${filename}`);
    let isExists = await exists(filePath);
    if (isExists) {
      // 删除临时上传文件
      if (fs.existsSync(file.path)) {
        fs.unlinkSync(file.path);
      }
      res.send({
        code: 0,
        codeText: "file is exists",
        originalFilename: filename,
        servicePath: filePath.replace(__dirname, HOSTNAME),
      });
      return;
    }
    writeFile(res, filePath, file, filename, true); // 不存在写入文件
  } catch (err) {
    console.log(err.toString(), 3434);
    res.send({
      code: 1,
      codeText: err,
    });
  }
});

// 单文件上传处理「BASE64」
app.post("/upload_single_base64", async (req, res) => {
  let file = decodeURIComponent(req.body.file); // 文件 base64 数据
  let filename = req.body.filename; // 文件名
  if (!filename || !file) {
    res.send({
      code: 1,
      codeText: "Missing request parameter",
    });
    return;
  }
  let spark = new SparkMD5.ArrayBuffer(); // w
  let suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1]; // 后缀名
  let isExists = false; // 是否已经存在
  let filepath;
  file = file.replace(/^data:image\/\w+;base64,/, "");
  file = Buffer.from(file, "base64"); // base64 转化为 buffer
  spark.append(file); // 转化为 hash 值
  filepath = path.resolve(uploadDir, `./${spark.end()}.${suffix}`); // 存储图片地址(后端处理名字)
  await delay();
  // 检测是否存在
  isExists = await exists(filepath); // 因为相同文件资源是固定的所有产生的 hash 值一样地址也一样
  if (isExists) {
    res.send({
      code: 0,
      codeText: "file is exists",
      originalFilename: filename,
      servicePath: filepath.replace(__dirname, HOSTNAME), // 存放返回已经存在文件
    });
    return;
  }
  writeFile(res, filepath, file, filename, false);
});

// 大文件切片上传 & 合并切片
const merge = function (HASH, count) {
  // HASH 及文件临时目录以及合并后文件名称
  return new Promise(async (resolve, reject) => {
    let filedir = path.resolve(uploadDir, `./${HASH}`); // 零四目录
    let suffix;
    let isExists = await exists(filedir);
    if (!isExists) {
      reject("HASH path is not found!"); // 没有直接出错
      return;
    }
    let fileList = fs.readdirSync(filedir); // 读取目录切片数据
    if (fileList.length < count) {
      reject("the slice has not been uploaded!"); // 缺少分片数据
      return;
    }
    fileList
      .sort((a, b) => {
        let reg = /_(\d+)/;
        return reg.exec(a)[1] - reg.exec(b)[1];
      }) // 先进行排序处理
      .forEach((item) => {
        !suffix ? (suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1]) : null; // 得到后缀名
        // 将切片进行拼接
        fs.appendFileSync(
          path.resolve(uploadDir, `./${HASH}.${suffix}`),
          fs.readFileSync(path.resolve(filedir, `./${item}`))
        );
        fs.unlinkSync(path.resolve(filedir, `./${item}`));
      });
    fs.rmdirSync(filedir); // 合并完成删除目录
    resolve({
      filepath: `${uploadDir}/${HASH}.${suffix}`,
      filename: `${HASH}.${suffix}`,
    });
  });
};
// 上传单个文件分片
app.post("/upload_chunk", async (req, res) => {
  try {
    let { fields, files } = await multiparty_upload(req);
    let file = (files.file && files.file[0]) || {};
    let filename = (fields.filename && fields.filename[0]) || "";
    let [, HASH] = /^([^_]+)_(\d+)/.exec(filename); // 获取上传后文件名
    // 创建存放切片的临时目录
    let filePath = path.resolve(uploadDir, `./${HASH}`);
    // 判断是否存在临时目录没有则创建
    !fs.existsSync(filePath) ? fs.mkdirSync(filePath) : null;
    // 把切片存储到临时目录中
    filePath = path.resolve(filePath, `./${filename}`);
    let isExists = await exists(filePath); // 切片查看是否存在
    if (isExists) {
      res.send({
        code: 0,
        codeText: "file is exists",
        originalFilename: filename,
        servicePath: filePath.replace(__dirname, HOSTNAME),
      });
      return;
    }
    writeFile(res, filePath, file, filename, true);
  } catch (err) {
    res.send({
      code: 1,
      codeText: err,
    });
  }
});
// 合并文件分片
app.post("/upload_merge", async (req, res) => {
  let { HASH, count } = req.body;
  try {
    let { filename, filepath } = await merge(HASH, count); // 进行将切片进行合并操作
    res.send({
      code: 0,
      codeText: "merge success",
      originalFilename: filename,
      servicePath: filepath.replace(__dirname, HOSTNAME),
    });
  } catch (err) {
    res.send({
      code: 1,
      codeText: err,
    });
  }
});
// 获取已上传片段
app.get("/upload_already", async (req, res) => {
  let { filename } = req.query;
  let fileList = [];
  try {
    let fileFath = path.resolve(uploadDir, `./${filename}`); // 直接先找文件
    const isExists = await exists(fileFath);
    if (isExists) {
      res.send({
        code: 0,
        codeText: "",
        exists: true,
        fileList: fileList,
      });
      return;
    }
    fileFath = path.resolve(uploadDir, `./${filename.split(".")[0]}`); // 查找临时目录
    fileList = fs.readdirSync(fileFath); // 读取目录内容
    fileList = fileList.sort((a, b) => {
      let reg = /_(\d+)/;
      return reg.exec(a)[1] - reg.exec(b)[1];
    }); // 进行排序处理
    res.send({
      code: 0,
      codeText: "",
      fileList: fileList,
    });
  } catch (err) {
    res.send({
      code: 0,
      codeText: "",
      fileList: fileList,
    });
  }
});

// 静态资源
app.use(express.static("./"));

// 找不到资源
app.use((req, res) => {
  res.status(404);
  res.send("NOT FOUND!");
});
